撰寫本系列文章目的在於提升資訊安全之實務能力,
並透過實作體悟到資訊安全領域的重要性,
本系列所有文章之內容皆有一定技術水平,
不得從事非法行為、惡意攻擊等非法活動,
「一切不合法規之行為皆受法律所約束」,
為了避免造成公司、廠商或玩家之間困擾,
所有實作不會拿已上市產品、Online Game 等等來作範例學習,
且部分具有深度、價值之內容,將會提升一定閱讀門檻(不對該技術做分析、解說),
請勿透過本系列文章所學,從事任何非法活動,請不要以身試法!!!
首先開始前要先說一下,
小弟我目前還屬於菜鳥階段,正不斷努力學習中,
若有發現錯誤或不妥之處還請不吝賜教。
歡迎大家多多留言,互相交流交流。
然後這篇文章不是從零開始講,所以有些東西需要某些基本知識才能完全掌握。
研究過或是寫過遊戲外掛的人,都應該會知道這個知名的「Blackbone」Library 吧?
Blackbone 是一個 Windows memory hacking library,
程式碼寫得挺漂亮的,可以直接拿來學習、使用(前提要先看懂,因為只是 library),
這個 Library 有非常多實用的功能,今天只有要講一個小功能:INJECT_DLL
首先,先來說一下從 Kernel mode(R0) 注入 DLL 的好處:
再來,根據我的經驗告訴我一件事,DLL Injection 不是一項 「等待被解決的問題」
What is 不是一項 「等待被解決的問題」 ???
意思是說 DLL Injection 這件事情本身就不是問題,
換句話說就是 DLL Injection 太容易了,手法非常非常多,
就技術面來看,基本上保護得再好都有方法能繞過,然後注入,
所以你/妳還在為 DLL Injection 這件事煩惱嗎?
接著,講一下這個 Project 所採用的三種注入技術:
(今天只講第一種,在寫下去內容太多,而且小弟我時間不多 >,<)
(有時間再來寫一篇)
首先,程式碼位於 Inject.c 的 BBInjectDll() 中,
一開始可以看到:
透過 PsLookupProcessByProcessId 拿 Process(注入目標) 的 EProcess
status = PsLookupProcessByProcessId( (HANDLE)pData->pid, &pProcess );
What is EProcess?
這個 EProcess Structure 長怎樣?要怎麼看?
!process 0 0
查看目前打開的所有 Process
lkd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS ffffc182be847040
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ab000 ObjectTable: ffff848ddf014040 HandleCount: 2192.
Image: System
PROCESS ffffc182bff3a5c0
SessionId: none Cid: 0144 Peb: 775efe7000 ParentCid: 0004
DirBase: 011b8000 ObjectTable: ffff848ddf5ab500 HandleCount: 52.
Image: smss.exe
--- --- --- --- --- ---
--- --- --- --- --- ---
--- --- --- --- --- ---
PROCESS ffffc182be948080
SessionId: 1 Cid: 0888 Peb: 9a982f9000 ParentCid: 11c0
DirBase: 10b2f000 ObjectTable: ffff848debb41600 HandleCount: 345.
Image: windbg.exe
PROCESS ffffc182c08835c0
SessionId: 1 Cid: 1528 Peb: d2ccd46000 ParentCid: 11c0
DirBase: 109a1000 ObjectTable: ffff848debc60800 HandleCount: 262.
Image: notepad.exe
dt _eprocess ffffc182c08835c0
ffffc182c08835c0
就是 notepad.exe 的 _EPROCESS 地址lkd> dt _eprocess ffffc182c08835c0
nt!_EPROCESS
+0x000 Pcb : _KPROCESS ? //剛剛說到的 KPROCESS
+0x2d8 ProcessLock : _EX_PUSH_LOCK
+0x2e0 UniqueProcessId : 0x00000000`00001528 Void
+0x2e8 ActiveProcessLinks : _LIST_ENTRY [ xxx - xxx ]
+0x2f8 RundownProtect : _EX_RUNDOWN_REF
--- --- --- --- --- ---
--- --- --- --- --- ---
--- --- --- --- --- ---
+0x82c MitigationFlags2 : 0
+0x82c MitigationFlags2Values : <unnamed-tag>
+0x830 PartitionObject : 0xffffc182`be8489f0 Void
//end
好的,現在講回來程式碼,還記得講到哪裡?... 剛拿到注入目標的 EProcess 而已。
繼續往下看會看到正在判斷 pData->type
是什麼類型,
第一個可以看到是 pData->type == IT_MMap
(不是這次要講的)
繼續往下看會看到正在拿 Ntdll.dll 的 Base
pNtdll = BBGetUserModule( pProcess, &ustrNtdll, isWow64 );
這邊有判斷目標是不是 Wow64,什麼?你/妳要問什麼是 Wow64 ?
這個不是本章重點,就略過去囉~
繼續往下看會看到正在拿 LdrLoadDll address(實作方法就不說惹)
LdrLoadDll = BBGetModuleExport( pNtdll, "LdrLoadDll", pProcess, NULL );
繼續往下看會看到厲害的東西:
if (PsIsProtectedProcess( pProcess ))
{
prot.pid = pData->pid;
prot.protection = Policy_Disable;
prot.dynamicCode = Policy_Disable;
prot.signature = Policy_Disable;
BBSetProtection( &prot );
}
這段 CODE 的用意就是要把 Process 保護拿掉,就是要把以下的值清空,
(這邊不帶大家看 CODE 了,有興趣的可以自己看、自己找)
在 Win10 16299 的 EProcess 中,
有個位置叫做:
+0x6ca Protection : _PS_PROTECTION
Protection 裡面有三樣東西:
[+0x000] Level : 0x0 [Type: unsigned char]
[+0x000 ( 2: 0)] Type : 0x0 [Type: unsigned char]
[+0x000 ( 3: 3)] Audit : 0x0 [Type: unsigned char]
[+0x000 ( 7: 4)] Signer : 0x0 [Type: unsigned char]
所以這些代表什麼? o(≧∀≦)o
好了,我都提到這邊了,請自行谷歌~(>_<。)\,
這個部分涉及部分技術,而且也不是本章重點就不深入討論囉~
繼續往下看會看到正在判斷 if (pData->type == IT_Thread)
耶!終於到了本章重點,LdrLoadDll + ZwCreateThreadEx
xxx pUserBuf = isWow64 ? xxxFunc( xxx ) : BBGetNativeCode( LdrLoadDll, &ustrPath );
if (pData->type == IT_Thread)
{
status = BBExecuteInNewThread(
pUserBuf,
NULL,
THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER,
pData->wait,
&threadStatus
);
先來看一下這一行:
xxx pUserBuf = isWow64 ? xxxFunc( xxx ) : BBGetNativeCode( LdrLoadDll, &ustrPath );
為了節省時間與文章長度,
直接假設程式走的是 BBGetNativeCode( LdrLoadDll, &ustrPath )
其中 ustrPath
是被注入的 DLL Full Path
跟進去可以看到有一段 shellcode,
這段 shellcode 的用意可以在註解中找到:
// <summary>
// Build injection code for native x64 process
// Must be running in target process context
// </summary>
UCHAR code[] =
{
0x48, 0x83, 0xEC, 0x28,
// sub rsp, 0x28
0x48, 0x31, 0xC9,
// xor rcx, rcx
0x48, 0x31, 0xD2,
// xor rdx, rdx
0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,
// mov r8, ModuleFileName offset +12
0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0,
// mov r9, ModuleHandle offset +22
0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,
// mov rax, LdrLoadDll offset +32
0xFF, 0xD0,
// call rax
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,
// mov rdx, COMPLETE_OFFSET offset +44
0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0,
// mov [rdx], CALL_COMPLETE
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,
// mov rdx, STATUS_OFFSET offset +60
0x89, 0x02,
// mov [rdx], eax
0x48, 0x83, 0xC4, 0x28,
// add rsp, 0x28
0xC3
// ret
};
繼續往下看會看到正在申請記憶體空間:
status = ZwAllocateVirtualMemory(
ZwCurrentProcess(),
&pBuffer,
0,
&size,
MEM_COMMIT,
PAGE_EXECUTE_READWRITE
);
繼續往下看會看到:
// Copy path
PUNICODE_STRING pUserPath = &pBuffer->path;
pUserPath->Length = 0;
pUserPath->MaximumLength = sizeof(pBuffer->buffer);
pUserPath->Buffer = pBuffer->buffer;
RtlUnicodeStringCopy( pUserPath, pPath );
// Copy code
memcpy( pBuffer, code, sizeof( code ) );
// Fill stubs
*(ULONGLONG*)((PUCHAR)pBuffer + 12) = (ULONGLONG)pUserPath;
*(ULONGLONG*)((PUCHAR)pBuffer + 22) = (ULONGLONG)&pBuffer->module;
*(ULONGLONG*)((PUCHAR)pBuffer + 32) = (ULONGLONG)LdrLoadDll;
*(ULONGLONG*)((PUCHAR)pBuffer + 44) = (ULONGLONG)&pBuffer->complete;
*(ULONGLONG*)((PUCHAR)pBuffer + 60) = (ULONGLONG)&pBuffer->status;
繼續往下看就是在 Init routine 了,最後恢復 Process 保護。
實際上到這邊 DLL Injection 就結束了,
不過再往下看的話會看到一些有趣的東西,
例如:
// Unlink module
if (pData->unlink)
// Erase header
if (pData->erasePE)
這是之後要講到的隱藏 Module 方法,
隱藏的方法非常多,之後會舉幾個有趣的例子。
最後整理了一下大致流程:
編譯與執行:
額外補充:
大家若有發現哪裡寫得不好或錯誤的地方,都留個言討論一下吧 XD
那我們下期見 o( ̄▽ ̄)ブ